
#ifndef TOKZRGENTR_GEN_REGEX_AUTOMATICS_GEN_H
#define TOKZRGENTR_GEN_REGEX_AUTOMATICS_GEN_H
#include"syntax_trees.h"
#include"ivisitors.h"
#include"automatics.h"
#include"char_set_table.h"

namespace TokzrGentr
{
	using regex_automatics_gen = class regex_automatics_gen;
	//#	automatics generator for regex.
	//#	See also visitor pattern.
	class regex_automatics_gen : public regex_node_ivisitor
	{
	private:
		temp_nfa _temp;
	public:
		regex_automatics_gen()
			:_temp({nullptr,nullptr}) {}
		void visit(char_set_node *node) override
		{
			auto start = nfa_alloc::alloc_status();
			auto finish = nfa_alloc::alloc_status();
			nfa_cstr::char_set_combine(start, finish, node->ucs_sets());
			_temp = { start,finish };
		}
		void visit(repeat_node *node) override
		{
			auto min = node->min_times();
			auto max = node->max_times();
			auto delta = max - min;

			//#	* kleen star
			if ((!node->is_finited())&&(min == 0)) {
				auto tmp = Call(node->operand().get());
				auto start = nfa_alloc::alloc_status();
				auto finish = nfa_alloc::alloc_status();
				nfa_cstr::epsilon_combine(start, tmp._start);
				nfa_cstr::epsilon_combine(tmp._finish, finish);
				nfa_cstr::epsilon_combine(finish, start);
				nfa_cstr::epsilon_combine(start, finish);
				_temp = { start,finish };
				return;
			}
			
			//#	for {0,m} 
			if (node->is_finited() && (min == 0)) 
			{
				auto tmp = Call(node->operand().get());
				auto finish = nfa_alloc::alloc_status();
				auto ts = nfa_alloc::alloc_status();
				nfa_cstr::epsilon_combine(ts, tmp._start);
				nfa_cstr::epsilon_combine(ts, finish);
				tmp = { ts,tmp._finish };
				for (size_t i = 1; i < delta; i++)
				{
					auto t = Call(node->operand().get());
					auto t_start = nfa_alloc::alloc_status();
					nfa_cstr::epsilon_combine(tmp._finish, t_start);
					nfa_cstr::epsilon_combine(t_start, t._start);
					nfa_cstr::epsilon_combine(t_start, finish);
					tmp = { tmp._start,t._finish };
				}
				nfa_cstr::epsilon_combine(tmp._finish,finish);
				_temp = { tmp._start,finish };
				return;
			}

			//#	for {n,m},{n,}
			auto min_part = Call(node->operand().get());
			for (size_t i = 1; i < min; i++) 
			{
				auto t = Call(node->operand().get());
				nfa_cstr::epsilon_combine(min_part._finish, t._start);
				min_part = { min_part._start,t._finish };
			}

			//# for{n,}
			if (!node->is_finited()) {
				auto kleen = Call(node->operand().get());
				auto k_start = nfa_alloc::alloc_status();
				auto k_finish = nfa_alloc::alloc_status();
				nfa_cstr::epsilon_combine(k_start, k_finish);
				nfa_cstr::epsilon_combine(k_finish, k_start);
				nfa_cstr::epsilon_combine(k_start, kleen._start);
				nfa_cstr::epsilon_combine(kleen._finish, k_finish);

				nfa_cstr::epsilon_combine(min_part._finish, k_start);
				_temp = { min_part._start,k_finish };
				return;
			}
			//#	for{n,m}
			else {
				if (min == max)
				{
					_temp = min_part;
					return;
				}
				auto tmp = Call(node->operand().get());
				auto finish = nfa_alloc::alloc_status();
				auto ts = nfa_alloc::alloc_status();
				nfa_cstr::epsilon_combine(ts, tmp._start);
				nfa_cstr::epsilon_combine(ts, finish);
				tmp = { ts,tmp._finish };
				for (size_t i = 1; i < delta; i++)
				{
					auto t = Call(node->operand().get());
					auto t_start = nfa_alloc::alloc_status();
					nfa_cstr::epsilon_combine(tmp._finish, t_start);
					nfa_cstr::epsilon_combine(t_start, t._start);
					nfa_cstr::epsilon_combine(t_start, finish);
					tmp = { tmp._start,t._finish };
				}
				nfa_cstr::epsilon_combine(tmp._finish, finish);
				nfa_cstr::epsilon_combine(min_part._finish, tmp._start);
				_temp = { min_part._start,finish };
				return;
			}
		}
		void visit(cater_node *node) override
		{
			auto lhs_nfa = Call(node->lhs().get());
			auto rhs_nfa = Call(node->rhs().get());

			nfa_cstr::epsilon_combine(lhs_nfa._finish, rhs_nfa._start);

			_temp = { lhs_nfa._start,rhs_nfa._finish };
		}
		void visit(alter_node *node) override
		{
			auto lhs_nfa = Call(node->lhs().get());
			auto rhs_nfa = Call(node->rhs().get());

			auto start = nfa_alloc::alloc_status();
			auto finish = nfa_alloc::alloc_status();

			nfa_cstr::epsilon_combine(start, lhs_nfa._start);
			nfa_cstr::epsilon_combine(start, rhs_nfa._start);
			nfa_cstr::epsilon_combine(lhs_nfa._finish, finish);
			nfa_cstr::epsilon_combine(rhs_nfa._finish, finish);

			_temp = { start,finish };
		}
		inline temp_nfa Call(regex_node *node) {
			node->accept(this);
			return _temp;
		}
	};

	class splict_chars_set_table_al : public regex_node_ivisitor
	{
	private:
		char_sets_table *_table;
	public:
		splict_chars_set_table_al(char_sets_table *table)
			:_table(table) {}
		void visit(alter_node *node) override 
		{
			Splict(node->lhs().get());
			Splict(node->rhs().get());
		}
		void visit(cater_node *node) override 
		{
			Splict(node->lhs().get());
			Splict(node->rhs().get());
		}
		void visit(char_set_node *node) override 
		{
			_table->add(node->ucs_sets());
		}
		void visit(repeat_node *node) override 
		{
			Splict(node->operand().get());
		}
		void Splict(regex_node *node)
		{
			node->accept(this);
		}
	};
}

#endif // !TOKZRGENTR_GEN_REGEX_AUTOMATICS_GEN_H
